home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1995 August / macformat-027.iso / mac / Shareware City / Developers / Oberon⁄F / Manuals / Views (.txt) < prev   
Encoding:
Oberon Document  |  1994-06-07  |  45.5 KB  |  705 lines  |  [oODC/obnF]

  1. Documents.StdDocumentDesc
  2. Documents.DocumentDesc
  3. Containers.ViewDesc
  4. Views.ViewDesc
  5. Stores.StoreDesc
  6. Documents.ModelDesc
  7. Containers.ModelDesc
  8. Models.ModelDesc
  9. Stores.ElemDesc
  10. TextViews.StdViewDesc
  11. TextViews.ViewDesc
  12. TextModels.StdModelDesc
  13. TextModels.ModelDesc
  14. TextModels.AttributesDesc
  15. Geneva
  16. TextRulers.StdRulerDesc
  17. TextRulers.RulerDesc
  18. TextRulers.StdStyleDesc
  19. TextRulers.StyleDesc
  20. TextRulers.AttributesDesc
  21. Geneva
  22. Geneva
  23. Geneva
  24. DevCommanders.StdViewDesc
  25. DevCommanders.ViewDesc
  26. Geneva
  27. HostPictures.StdViewDesc
  28. Geneva
  29. Geneva
  30. Geneva
  31. window
  32. child window
  33.     text view
  34. text model
  35. frame for text view
  36. frame for text view
  37.     text view
  38. Geneva
  39. window
  40. child window
  41.     text view
  42. text model
  43. graphic view
  44. frame for text view
  45. frame for (
  46. graphic view
  47. frame for (
  48. graphic view
  49. graphic model
  50.     text view
  51. frame for text view
  52. 4 View Programming
  53. In this chapter, we present a sequence of six increasingly more versatile versions of a "Hello World" view object. Views are the central abstraction in Oberon/F. Everything revolves around views: commands operate on views, windows display views, views can be internalized and externalized as well as displayed, and views may be embedded into other views.
  54. The first version of our "Hello World" view is about the simplest possible view implementation. Its major components are the definition of a Views.View extension, the implementation of a Views.Restore procedure which draws the contents of the view, and a command procedure which allocates, initializes, and opens the view in a window:
  55. MODULE TutorialEx11;
  56. (* Open a simple view which displays the string "Hello World" *)
  57.     IMPORT Fonts, Ports, Views;
  58.     CONST d = 20 * Ports.point;
  59.     TYPE
  60.         View = POINTER TO ViewDesc;
  61.         ViewDesc = RECORD (Views.ViewDesc) END;
  62.     PROCEDURE (v: View) Restore (f: Views.Frame; l, t, r, b: LONGINT);
  63.     BEGIN
  64.         f.DrawString(d, d, Ports.black, "Hello World", Fonts.dir.System())
  65.     END Restore;
  66.     PROCEDURE Deposit*;
  67.         VAR v: View;
  68.     BEGIN
  69.         NEW(v); v.Init(); Views.Deposit(v)
  70.     END Deposit;
  71. END TutorialEx11.
  72. To execute this program, invoke the following command:
  73.  "TutorialEx11.Deposit; Open"
  74. This simple program is completely functional already. The procedure TutorialEx11.Deposit puts a newly allocated view into a queue. The pseudo-procedure Open in the command string above removes a view from the queue, and opens it into a window. Alternatively, the view could be pasted:
  75.  "TutorialEx11.Deposit; PasteView"
  76. The pseudo-procedure PasteView removes a view from the queue, but pastes it into the focus view.
  77. But let us return to the first command, which opened a window. The window's contents can be saved in a file, and this file can be opened again using the standard Open... menu entry. Printing the view is already possible as well.
  78. Picture 4a  Result of "TutorialEx11.Deposit; Open"
  79. Every view performs its output operations and mouse/keyboard polling via a Views.Frame object. In the above example, TutorialEx11.View.Restore uses its frame parameter f to draw a string. In analogy to the file system, a frame can be regarded as a mapper object, in this case for both input and output simultaneously. A frame embodies coordinate transformations and clipping facilities. An important property of Oberon/F frames can be stated here:
  80. In each window, there is exactly one frame for every (at least partially) visible view.
  81. The next version of our view implementation does not display the constant string "Hello World", instead the user may type in an arbitrary string (the string's length is limited to 255 characters, but no error handling is performed in these demonstration programs).
  82. MODULE TutorialEx12;
  83. (* Same as TutorialEx11, but an arbitrary string is displayed which can be typed in *)
  84.     IMPORT Fonts, Ports, Views, Controllers;
  85.     CONST d = 20 * Ports.point;
  86.     TYPE
  87.         View = POINTER TO ViewDesc;
  88.         ViewDesc = RECORD (Views.ViewDesc)
  89.             i: INTEGER;                    (* position of next free slot in string *)
  90.             s: ARRAY 256 OF CHAR        (* string *)
  91.         END;
  92.     PROCEDURE (v: View) Restore (f: Views.Frame; l, t, r, b: LONGINT);
  93.     BEGIN
  94.         f.DrawString(d, d, Ports.black, v.s, Fonts.dir.System())
  95.     END Restore;
  96.     PROCEDURE (v: View) HandleCtrlMsg (f: Views.Frame; VAR msg: Controllers.Message;
  97.                                             VAR focus: Views.View);
  98.     BEGIN
  99.         WITH msg: Controllers.PollOpsMsg DO
  100.             msg.valid := {Controllers.pasteChar}        (* tell that v accepts typing *)
  101.         | msg: Controllers.EditMsg DO
  102.             IF msg.op = Controllers.pasteChar THEN    (* accept typing *)
  103.                 v.s[v.i] := msg.char; INC(v.i); v.s[v.i] := 0X;    (* append character to string *)
  104.                 Views.Update(v, Views.keepFrames)    (* restore v in any frame that displays it *)
  105.             END
  106.         ELSE                                (* ignore other messages *)
  107.         END
  108.     END HandleCtrlMsg;
  109.     PROCEDURE Deposit*;
  110.         VAR v: View;
  111.     BEGIN
  112.         NEW(v); v.Init();
  113.         v.s := ""; v.i := 0;
  114.         Views.Deposit(v)
  115.     END Deposit;
  116. END TutorialEx12.
  117. A typed character is sent to a view in the form of a controller message record (Controllers.Message), to be handled by the view's HandleCtrlMsg procedure. This procedure reacts by inserting the character contained in the message into a string field s of the view record. Afterwards, it causes the view to be restored, i.e. wherever the view is visible on the display it is redrawn in its new state, displaying the string that has become one character longer.
  118. Note that the HandleCtrlMsg is called with a Controllers.EditMsg as actual parameter when a character was typed in. However, a Controllers.PollOpsMsg may be sent to the view as well. Such a message inquires about the operations that a view supports. In our case, typing ("pasting") a character is the only operation supported.
  119. In the above example, a view contains no constant state (the "Hello World" string) anymore, but rather a variable state (the view's string field s). Thus this state needs to be saved in the file when the window's contents are saved to disk. For this purpose, a view provides two procedures, called Internalize and Externalize, whose uses are shown in the next iteration of our example program:
  120. MODULE TutorialEx13;
  121. (* Same as TutorialEx12, but the view's string can be stored and copied *)
  122.     IMPORT Fonts, Ports, Stores, Views, Controllers;
  123.     CONST d = 20 * Ports.point;
  124.     TYPE
  125.         View = POINTER TO ViewDesc;
  126.         ViewDesc = RECORD (Views.ViewDesc)
  127.             i: INTEGER;                    (* position of next free slot in string *)
  128.             s: ARRAY 256 OF CHAR        (* string *)
  129.         END;
  130.     PROCEDURE (v: View) Internalize (VAR rd: Stores.Reader);
  131.         VAR version: SHORTINT;
  132.     BEGIN
  133.         v.Internalize^(rd);                (* internalize the base type *)
  134.         IF rd.cancelled THEN RETURN END;
  135.         rd.ReadVersion(0, 0, version);
  136.         IF rd.cancelled THEN RETURN END;
  137.         rd.ReadInt(v.i); rd.ReadString(v.s)
  138.     END Internalize;
  139.     PROCEDURE (v: View) Externalize (VAR wr: Stores.Writer);
  140.     BEGIN
  141.         v.Externalize^(wr);            (* externalize the base type *)
  142.         wr.WriteVersion(0);
  143.         wr.WriteInt(v.i); wr.WriteString(v.s)
  144.     END Externalize;
  145.     PROCEDURE (v: View) CopyFrom (source: Views.View);
  146.     BEGIN
  147.         v.CopyFrom^(source);        (* copy the base type *)
  148.         WITH source: View DO
  149.             v.i := source.i; v.s := source.s
  150.         END
  151.     END CopyFrom;
  152.     PROCEDURE (v: View) Restore (f: Views.Frame; l, t, r, b: LONGINT);
  153.     BEGIN
  154.         f.DrawString(d, d, Ports.black, v.s, Fonts.dir.System())
  155.     END Restore;
  156.     PROCEDURE (v: View) HandleCtrlMsg (f: Views.Frame; VAR msg: Controllers.Message; VAR focus: Views.View);
  157.     BEGIN
  158.         WITH msg: Controllers.PollOpsMsg DO
  159.             msg.valid := {Controllers.pasteChar}        (* tell that v accepts typing *)
  160.         | msg: Controllers.EditMsg DO
  161.             IF msg.op = Controllers.pasteChar THEN    (* accept typing *)
  162.                 Views.BeginModification(v);        (* mark view's document as dirty *)
  163.                 v.s[v.i] := msg.char; INC(v.i); v.s[v.i] := 0X;    (* append character to string *)
  164.                 Views.EndModification(v);
  165.                 Views.Update(v, Views.keepFrames)        (* restore v in any frame that displays it *)
  166.             END
  167.         ELSE                                (* ignore other messages *)
  168.         END
  169.     END HandleCtrlMsg;
  170.     PROCEDURE Deposit*;
  171.         VAR v: View;
  172.     BEGIN
  173.         NEW(v); v.Init();
  174.         v.s := ""; v.i := 0;
  175.         Views.Deposit(v)
  176.     END Deposit;
  177. END TutorialEx13.
  178. A few comments about View.Internalize and View.Externalize are in order here: First, the two procedures have a reader, respectively a writer, as variable parameters. As mentioned earlier in the files examples, these file mappers are set up by Oberon/F itself, a view simply uses them.
  179. Second, these procedures must call their base procedures ("super calls") such that the base types get the opportunity to read/write their own data.
  180. Third, View.Internalize must read exactly the same (amount of) data that View.Externalize has written.
  181. Fourth, a user interface typically defines some visual distinction for documents that contain modified views, i.e. for "dirty" documents. Or it may require that when the user closes a dirty document, he should be asked whether to save it or not. In order to make this possible, a view must tell when it has been modified. This is done by the Views.BeginModification /Views.EndModification calls.
  182. In addition to View.Internalize and View.Externalize, the above view implementation also implements a CopyFrom procedure. Such a procedure should copy the view's contents, given a source view of the same type. CopyFrom should have the same effect as if the source's contents were externalized on a temporary file, and then internalized again by the destination view.
  183. Basically, TutorialEx13 has shown most of what is involved in implementing a simple view class. Such simple views are often sufficient, consequently it is important that they are easy to implement. However, there are cases where a more advanced view design is in order. In particular, if a window normally can only display a small part of a view's contents, multi-view editing should be supported.
  184. Multi-view editing means that there may be several views showing the same data. The typical application of this feature is to have two or more windows displaying the same document, each of these windows showing a different part of the document, in its own view. Thus, if a view's data has been changed, it and all the other affected views must be notified of the change, such that they can update the display accordingly.
  185. The following sample program, which is the same as the previous one except that it supports multi-view editing, is roughly twice as long. This indicates that the design and implementation of such views is quite a bit more involved than that of simple views. It is a major design decision whether this additional complexity is warranted by its increased convenience.
  186. MODULE TutorialEx14;
  187. (* Same as TutorialEx13, but uses a separate model for the string *)
  188.     IMPORT Fonts, Ports, Stores, Models, Views, Controllers;
  189.     CONST d = 20 * Ports.point;
  190.     TYPE
  191.         Model = POINTER TO ModelDesc;
  192.         ModelDesc = RECORD (Models.ModelDesc)
  193.             i: INTEGER;                (* position of next free slot in string *)
  194.             s: ARRAY 256 OF CHAR    (* string *)
  195.         END;
  196.         View = POINTER TO ViewDesc;
  197.         ViewDesc = RECORD (Views.ViewDesc)
  198.             m: Model
  199.         END;
  200.     (* Model *)
  201.     PROCEDURE (m: Model) Clone (): Model;
  202.         VAR s: Stores.Store;
  203.     BEGIN
  204.         s := Stores.Clone(m); RETURN s(Model)
  205.     END Clone;
  206.     PROCEDURE (m: Model) Internalize (VAR rd: Stores.Reader);
  207.         VAR version: SHORTINT;
  208.     BEGIN
  209.         m.Internalize^(rd);        (* internalize the base type *)
  210.         IF rd.cancelled THEN RETURN END;
  211.         rd.ReadVersion(0, 0, version);
  212.         IF rd.cancelled THEN RETURN END;
  213.         rd.ReadInt(m.i); rd.ReadString(m.s)
  214.     END Internalize;
  215.     PROCEDURE (m: Model) Externalize (VAR wr: Stores.Writer);
  216.     BEGIN
  217.         m.Externalize^(wr);        (* externalize the base type *)
  218.         wr.WriteVersion(0);
  219.         wr.WriteInt(m.i); wr.WriteString(m.s)
  220.     END Externalize;
  221.     PROCEDURE (m: Model) CopyFrom (source: Model);
  222.     BEGIN
  223.         m.i := source.i; m.s := source.s; m.Init
  224.     END CopyFrom;
  225.     (* View *)
  226.     PROCEDURE (v: View) InitModel (m: Model);
  227.     BEGIN
  228.         v.m := m
  229.     END InitModel;
  230.     PROCEDURE (v: View) Internalize (VAR rd: Stores.Reader);
  231.         VAR version: SHORTINT; s: Stores.Store;
  232.     BEGIN
  233.         v.Internalize^(rd);            (* internalize the base type *)
  234.         IF rd.cancelled THEN RETURN END;
  235.         rd.ReadVersion(0, 0, version);
  236.         IF rd.cancelled THEN RETURN END;
  237.         rd.ReadStore(s);
  238.         v.InitModel(s(Model))
  239.     END Internalize;
  240.     PROCEDURE (v: View) Externalize (VAR wr: Stores.Writer);
  241.     BEGIN
  242.         v.Externalize^(wr);            (* externalize the base type *)
  243.         wr.WriteVersion(0);
  244.         wr.WriteStore(v.m)
  245.     END Externalize;
  246.     PROCEDURE (v: View) Restore (f: Views.Frame; l, t, r, b: LONGINT);
  247.     BEGIN
  248.         f.DrawString(d, d, Ports.black, v.m.s, Fonts.dir.System())
  249.     END Restore;
  250.     PROCEDURE (v: View) CopyModelFrom (source: Views.View; shallow: BOOLEAN);
  251.         VAR m: Model;
  252.     BEGIN
  253.         v.CopyModelFrom^(source, shallow);
  254.         WITH source: View DO
  255.             IF shallow THEN
  256.                 m := source.m        (* both views share the same model *)
  257.             ELSE
  258.                 m := source.m.Clone(); m.CopyFrom(source.m)    (* both views get their own model copies *)
  259.             END;
  260.             v.InitModel(m)
  261.         END
  262.     END CopyModelFrom;
  263.     PROCEDURE (v: View) ThisModel (): Models.Model;
  264.     BEGIN
  265.         RETURN v.m
  266.     END ThisModel;
  267.     PROCEDURE (v: View) HandleModelMsg (VAR msg: Models.Message);
  268.     BEGIN
  269.         Views.Update(v, Views.keepFrames)    (* restore v in any frame that displays it *)
  270.     END HandleModelMsg;
  271.     PROCEDURE (v: View) HandleCtrlMsg (f: Views.Frame; VAR msg: Controllers.Message; VAR focus: Views.View);
  272.         VAR m: Model; umsg: Models.UpdateMsg;
  273.     BEGIN
  274.         WITH msg: Controllers.PollOpsMsg DO
  275.             msg.valid := {Controllers.pasteChar}    (* tell that v accepts typing *)
  276.         | msg: Controllers.EditMsg DO
  277.             IF msg.op = Controllers.pasteChar THEN
  278.                 Models.BeginModification(v.m);    (* mark view's document as dirty *)
  279.                 m := v.m;
  280.                 m.s[m.i] := msg.char; INC(m.i);
  281.                 m.s[m.i] := 0X;        (* append character to string *)
  282.                 Models.EndModification(m);
  283.                 Models.Broadcast(m, umsg)    (* update all views on this model *)
  284.             END
  285.         ELSE                            (* ignore other messages *)
  286.         END
  287.     END HandleCtrlMsg;
  288.     PROCEDURE Deposit*;
  289.         VAR v: View; m: Model;
  290.     BEGIN
  291.         NEW(v); v.Init();
  292.         NEW(m); m.Init; v.InitModel(m); v.m.s := ""; v.m.i := 0;
  293.         Views.Deposit(v)
  294.     END Deposit;
  295. END TutorialEx14.
  296. Multi-view editing is realized by factoring out the view's data into a separate data structure, called a model. This model is shared between all its views, i.e. each such view contains a pointer to the model:
  297. Picture 4b  Two Views on one Model
  298. A model is, like a view, an extension of a Stores.Store, which is the base type for all extensible and persistent objects. Stores define the Internalize and Externalize procedures we've already met for a view. In TutorialEx14, the model stores the string and its length, while the view stores the whole model! For this purpose, a Stores.Writer provides a WriteStore and a Stores.Reader provides a ReadStore procedure.
  299. As a consequence of this separation, copying the string is now done by the model, while the CopyModelFrom of the view copies the model itself. A model, like any store, has a generic Clone procedure, which returns a new object of the same type as the object being cloned. However, the new object is neither copied nor even initialized!
  300. The TutorialEx14 example introduces an auxiliary procedure View.InitModel, which assigns a model to a non-initialized view.
  301. For views which are capable of multi-view editing, copying can mean several things. CopyFrom as implemented in the previous example copies the view-specific state, i.e. the state which is private to each view, and not shared like the model. CopyModelFrom copies the shared state, usually this is just the model. It can do this in two different ways: either it copies only the pointer to the model, this is called a shallow copy. Or, it clones the model, copies its contents, and assigns the clone. This is called a deep copy. After a deep copy, the source and its copy become independent, since they don't share any state.
  302. View.ThisModel returns the model of the view. Its default implementation returns NIL, which should be overridden in views that contain models. This procedure is used by the framework to find all views that display a given model, in order to implement model broadcasts, as described in the next paragraph.
  303. When a model changes, all views displaying it must be updated. For this purpose, a model broadcast is generated: A model broadcast notifies all views which display a particular model about a model modification that has happened. This gives each view the opportunity to restore its contents on the screen. A model broadcast is generated in the View.HandleCtrlMsg procedure by calling Models.Broadcast. The message is received by every view which contains the correct model, via its View.HandleModelMsg procedure.
  304. This indirection becomes important if different types of views display the same model, e.g. a tabular view and a graphical view on a spreadsheet model; and if instead of restoring the whole view - as has been done in our examples - only the necessary parts of the views are restored. These necessary parts may be completely different for different types of views.
  305. A very sophisticated model may be able to contain ("embed") arbitrary views as part of its contents. Such a container view implements recursive view embedding. For example, a text view may display a text model which contains not only characters (its intrinsic contents), but also graphical or other views flowing along in the text stream.
  306. The combination of multi-view editing and recursive view embedding can lead to the following situation, where two text views show the same text model, which contains a graphics view. Each text view lives in its own window and thus has its own frame. The graphics view is unique however, since it is embedded in the text model, which is shared by both views. Nevertheless the graphics can be visible in both text views simultaneously, and thus there can be two frames for this one view:
  307. Picture 4c  Two Frames on one View
  308. As a consequence, one and the same view may be visible in several places on the screen simultaneously. In this case, this means that when the view has changed, several places must be updated: the view must restore the necessary area once for every frame on this view. As a consequence, a notification mechanism must exist which lets the view update each of its frames. This is done in a similar way as the notification mechanism for model changes: with a view broadcast. Fortunately, there is a standard update mechanism in the framework which automatically handles this second broadcast level (see below).
  309. We now can summarize the typical events that occur when a user interacts with a view:
  310. Controller message handling:
  311. 1) Some controller message is sent to the focus view.
  312. 2) The focus view interprets the message and changes its model accordingly.
  313. 3) The model broadcasts a model message, describing the change that has been performed.
  314. Model message handling:
  315. 4) Every view on this model receives the model message.
  316. 5) It determines how its display should change because of this model modification.
  317. 6) It broadcasts a view message, describing how its display should change.
  318. View message handling:
  319. 7) The view receives the view notification message once for every frame on this view.
  320. 8) Every time, the view redraws the frame contents according to the view message.
  321. This mechanism is fundamental to Oberon/F. If you have understood it, you have mastered the most complicated part of a view implementation. Interpreting the various controller messages, which are defined in module Controllers, is more a matter of diligence than of understanding difficult new concepts. Moreover, you only need to interpret the controller messages in which you are interested, because messages which are not relevant can simply be ignored.
  322. If the necessary display change can be realized through restoration of some area, steps 6, 7, and 8 are handled by the framework: the view only calls Views.Update if a whole view should be updated, or Views.UpdateIn if some rectangular area should be updated. Calls of these update procedures cause a lazy update, i.e. the framework adds up the region to be updated, and causes a restore of this region when the current command has terminated.
  323. A view programmer needs to use view messages only if no complete restore is desired. This is the case only for marks like selection, focus, or caret marks. These marks can sometimes be handled more efficiently because they are involutory: applied twice, they have no effect. Thus marks are often switched on and off through custom view messages, not through the slower update mechanism.
  324. Now that we have seen the crucial ingredients of a view implementation, several less central features can be presented. The next one is a variation of the above program, in which a controller message is not interpreted directly. Instead, an operation object is created and then executed. An operation provides a do / undo / redo capability, as shown in the program below:
  325. MODULE TutorialEx15;
  326. (* Same as TutorialEx14, but generate undoable operations for character insertion *)
  327.     IMPORT Domains, Fonts, Ports, Stores, Models, Views, Controllers;
  328.     CONST d = 20 * Ports.point;
  329.     TYPE
  330.         Model = POINTER TO ModelDesc;
  331.         ModelDesc = RECORD (Models.ModelDesc)
  332.             i: INTEGER;                (* position of next free slot in string *)
  333.             s: ARRAY 256 OF CHAR    (* string *)
  334.         END;
  335.         View = POINTER TO ViewDesc;
  336.         ViewDesc = RECORD (Views.ViewDesc)
  337.             m: Model
  338.         END;
  339.         PasteCharOp = POINTER TO PasteCharOpDesc;
  340.         PasteCharOpDesc = RECORD (Domains.OperationDesc)
  341.             m: Model;
  342.             char: CHAR;
  343.             do: BOOLEAN
  344.         END;
  345.     (* PasteCharOp *)
  346.     PROCEDURE (op: PasteCharOp) Do;
  347.         VAR m: Model; msg: Models.UpdateMsg;
  348.     BEGIN
  349.         m := op.m;
  350.         IF op.do THEN                (* do operation's transformation *)
  351.             m.s[m.i] := op.char; INC(m.i)
  352.         ELSE                            (* undo operation's transformation *)
  353.             DEC(m.i)                    (* remove character from string *)
  354.         END;
  355.         m.s[m.i] := 0X;
  356.         op.do := ~op.do;            (* toggle between "do" and "undo" *)
  357.         Models.Broadcast(m, msg)    (* update all views on this model *)
  358.     END Do;
  359.     PROCEDURE NewPasteCharOp (m: Model; char: CHAR): PasteCharOp;
  360.         VAR op: PasteCharOp;
  361.     BEGIN
  362.         NEW(op); op.m := m; op.char := char; op.do := TRUE; RETURN op
  363.     END NewPasteCharOp;
  364.     (* Model *)
  365.     PROCEDURE (m: Model) Clone (): Model;
  366.         VAR s: Stores.Store;
  367.     BEGIN
  368.         s := Stores.Clone(m); RETURN s(Model)
  369.     END Clone;
  370.     PROCEDURE (m: Model) Internalize (VAR rd: Stores.Reader);
  371.         VAR version: SHORTINT;
  372.     BEGIN
  373.         m.Internalize^(rd);            (* internalize the base type *)
  374.         IF rd.cancelled THEN RETURN END;
  375.         rd.ReadVersion(0, 0, version);
  376.         IF rd.cancelled THEN RETURN END;
  377.         rd.ReadInt(m.i); rd.ReadString(m.s)
  378.     END Internalize;
  379.     PROCEDURE (m: Model) Externalize (VAR wr: Stores.Writer);
  380.     BEGIN
  381.         m.Externalize^(wr);            (* externalize the base type *)
  382.         wr.WriteVersion(0);
  383.         wr.WriteInt(m.i); wr.WriteString(m.s)
  384.     END Externalize;
  385.     PROCEDURE (m: Model) CopyFrom (source: Model);
  386.     BEGIN
  387.         m.i := source.i; m.s := source.s; m.Init
  388.     END CopyFrom;
  389.     (* View *)
  390.     PROCEDURE (v: View) InitModel (m: Model);
  391.     BEGIN
  392.         v.m := m
  393.     END InitModel;
  394.     PROCEDURE (v: View) Internalize (VAR rd: Stores.Reader);
  395.         VAR version: SHORTINT; s: Stores.Store;
  396.     BEGIN
  397.         v.Internalize^(rd);            (* internalize the base type *)
  398.         IF rd.cancelled THEN RETURN END;
  399.         rd.ReadVersion(0, 0, version);
  400.         IF rd.cancelled THEN RETURN END;
  401.         rd.ReadStore(s);                (* read a store *)
  402.         v.InitModel(s(Model))
  403.     END Internalize;
  404.     PROCEDURE (v: View) Externalize (VAR wr: Stores.Writer);
  405.     BEGIN
  406.         v.Externalize^(wr);            (* externalize the base type *)
  407.         wr.WriteVersion(0);
  408.         wr.WriteStore(v.m)
  409.     END Externalize;
  410.     PROCEDURE (v: View) CopyModelFrom (source: Views.View; shallow: BOOLEAN);
  411.         VAR m: Model;
  412.     BEGIN
  413.         v.CopyModelFrom^(source, shallow);    (* copy the base type *)
  414.         WITH source: View DO
  415.             IF shallow THEN
  416.                 m := source.m        (* both views share the same model *)
  417.             ELSE
  418.                 m := source.m.Clone(); m.CopyFrom(source.m)    (* both views get their own model copies *)
  419.             END;
  420.             v.InitModel(m)
  421.         END
  422.     END CopyModelFrom;
  423.     PROCEDURE (v: View) Restore (f: Views.Frame; l, t, r, b: LONGINT);
  424.     BEGIN
  425.         f.DrawString(d, d, Ports.black, v.m.s, Fonts.dir.System())
  426.     END Restore;
  427.     PROCEDURE (v: View) ThisModel (): Models.Model;
  428.     BEGIN
  429.         RETURN v.m
  430.     END ThisModel;
  431.     PROCEDURE (v: View) HandleModelMsg (VAR msg: Models.Message);
  432.     BEGIN
  433.         Views.Update(v, Views.keepFrames)    (* restore v in any frame that displays it *)
  434.     END HandleModelMsg;
  435.     PROCEDURE (v: View) HandleCtrlMsg (f: Views.Frame; VAR msg: Controllers.Message; VAR focus: Views.View);
  436.         VAR op: Domains.Operation;
  437.     BEGIN
  438.         WITH msg: Controllers.PollOpsMsg DO
  439.             msg.valid := {Controllers.pasteChar}    (* tell that v accepts typing *)
  440.         | msg: Controllers.EditMsg DO
  441.             IF msg.op = Controllers.pasteChar THEN
  442.                 op := NewPasteCharOp(v.m, msg.char);    (* generate operation *)
  443.                 Models.Do(v.m, "typing", op)    (* execute operation *)
  444.             END
  445.         ELSE                            (* ignore other messages *)
  446.         END
  447.     END HandleCtrlMsg;
  448.     PROCEDURE Deposit*;
  449.         VAR v: View; m: Model;
  450.     BEGIN
  451.         NEW(v); v.Init();
  452.         NEW(m); m.Init; v.InitModel(m); v.m.s := ""; v.m.i := 0;
  453.         Views.Deposit(v)
  454.     END Deposit;
  455. END TutorialEx15.
  456. As a last version of our sample views, we modify the previous variant by exporting the Model and View types, and by separating interface and implementation of these two types. In order to do the latter, so-called directory objects are introduced, which generate (hidden) default implementations of abstract data types. The way that operations are handled is modified as well.
  457. Real Oberon/F subsystems would additionally split the module into two modules, one for the model and one for the view. A third module with commands might be introduced, if the number and complexity of commands warrant it (e.g. TextModels, TextViews, and TextCmds). Even more complicated views would further split views into views and controllers, each in its own module. However, these are advanced topics not covered in this documentation.
  458. MODULE TutorialEx16;
  459. (* Same as TutorialEx15, but interfaces and implementations separated, and operation directly in Insert procedure *)
  460.     IMPORT Domains, Fonts, Ports, Stores, Models, Views, Controllers;
  461.     CONST d = 20 * Ports.point;
  462.     TYPE
  463.         Model* = POINTER TO ModelDesc;
  464.         ModelDesc* = RECORD (Models.ModelDesc) END;
  465.         ModelDirectory* = POINTER TO ModelDirectoryDesc;
  466.         ModelDirectoryDesc* = RECORD END;
  467.         View* = POINTER TO ViewDesc;
  468.         ViewDesc* = RECORD (Views.ViewDesc) END;
  469.         Directory* = POINTER TO DirectoryDesc;
  470.         DirectoryDesc* = RECORD END;
  471.         StdModel = POINTER TO StdModelDesc;
  472.         StdModelDesc = RECORD (ModelDesc)
  473.             i: INTEGER;                (* position of next free slot in string *)
  474.             s: ARRAY 256 OF CHAR    (* string *)
  475.         END;
  476.         StdModelDirectory = POINTER TO StdModelDirectoryDesc;
  477.         StdModelDirectoryDesc = RECORD (ModelDirectoryDesc) END;
  478.         StdView = POINTER TO StdViewDesc;
  479.         StdViewDesc = RECORD (ViewDesc)
  480.             m: Model
  481.         END;
  482.         StdDirectory = POINTER TO StdDirectoryDesc;
  483.         StdDirectoryDesc = RECORD (DirectoryDesc) END;
  484.         PasteCharOp = POINTER TO PasteCharOpDesc;
  485.         PasteCharOpDesc = RECORD (Domains.OperationDesc)
  486.             m: StdModel;
  487.             char: CHAR;
  488.             do: BOOLEAN
  489.         END;
  490.         mdir-: ModelDirectory;
  491.         dir-: Directory;
  492.     (* Model *)
  493.     PROCEDURE (m: Model) Clone* (): Model;
  494.         VAR s: Stores.Store;
  495.     BEGIN
  496.         s := Stores.Clone(m); RETURN s(Model)
  497.     END Clone;
  498.     PROCEDURE (m: Model) Internalize* (VAR rd: Stores.Reader);
  499.         VAR version: SHORTINT;
  500.     BEGIN
  501.         m.Internalize^(rd);
  502.         IF rd.cancelled THEN RETURN END;
  503.         rd.ReadVersion(0, 0, version)
  504.     END Internalize;
  505.     PROCEDURE (m: Model) Externalize* (VAR wr: Stores.Writer);
  506.     BEGIN
  507.         m.Externalize^(wr);
  508.         wr.WriteVersion(0)
  509.     END Externalize;
  510.     PROCEDURE (m: Model) CopyFrom* (source: Model);
  511.     BEGIN
  512.         m.Init
  513.     END CopyFrom;
  514.     PROCEDURE (m: Model) Insert* (char: CHAR);
  515.     BEGIN
  516.         HALT(127)
  517.     END Insert;
  518.     PROCEDURE (m: Model) Remove*;
  519.     BEGIN
  520.         HALT(127)
  521.     END Remove;
  522.     PROCEDURE (m: Model) GetString* (VAR s: ARRAY OF CHAR);
  523.     BEGIN
  524.         HALT(127)
  525.     END GetString;
  526.     (* ModelDirectory *)
  527.     PROCEDURE (d: ModelDirectory) New* (): Model;
  528.     BEGIN
  529.         HALT(127)
  530.     END New;
  531.     (* PasteCharOp *)
  532.     PROCEDURE (op: PasteCharOp) Do;
  533.         VAR m: StdModel; msg: Models.UpdateMsg;
  534.     BEGIN
  535.         m := op.m;
  536.         IF op.do THEN                (* do operation's transformation *)
  537.             m.s[m.i] := op.char; INC(m.i);
  538.         ELSE                            (* undo operation's transformation *)
  539.             DEC(m.i)                    (* remove character from string *)
  540.         END;
  541.         m.s[m.i] := 0X;
  542.         op.do := ~op.do;            (* toggle between "do" and "undo" *)
  543.         Models.Broadcast(m, msg)    (* update all views on this model *)
  544.     END Do;
  545.     (* StdModel *)
  546.     PROCEDURE (m: StdModel) Internalize (VAR rd: Stores.Reader);
  547.         VAR version: SHORTINT;
  548.     BEGIN
  549.         m.Internalize^(rd);            (* internalize the base type *)
  550.         IF rd.cancelled THEN RETURN END;
  551.         rd.ReadVersion(0, 0, version);
  552.         IF rd.cancelled THEN RETURN END;
  553.         rd.ReadInt(m.i); rd.ReadString(m.s)
  554.     END Internalize;
  555.     PROCEDURE (m: StdModel) Externalize (VAR wr: Stores.Writer);
  556.     BEGIN
  557.         m.Externalize^(wr);            (* externalize the base type *)
  558.         wr.WriteVersion(0);
  559.         wr.WriteInt(m.i); wr.WriteString(m.s)
  560.     END Externalize;
  561.     PROCEDURE (m: StdModel) CopyFrom (source: Model);
  562.     BEGIN
  563.         m.CopyFrom^(source);
  564.         WITH source: StdModel DO
  565.             m.i := source.i; m.s := source.s
  566.         END
  567.     END CopyFrom;
  568.     PROCEDURE (m: StdModel) Insert (char: CHAR);
  569.         VAR op: PasteCharOp;
  570.     BEGIN
  571.         NEW(op); op.m := m; op.char := char; op.do := TRUE; Models.Do(m, "insertion", op)
  572.     END Insert;
  573.     PROCEDURE (m: StdModel) Remove;
  574.         VAR msg: Models.UpdateMsg;
  575.     BEGIN
  576.         DEC(m.i); m.s[m.i] := 0X;
  577.         Models.Broadcast(m, msg)    (* update all views on this model *)
  578.     END Remove;
  579.     PROCEDURE (m: StdModel) GetString (VAR s: ARRAY OF CHAR);
  580.     BEGIN
  581.         COPY(m.s, s)
  582.     END GetString;
  583.     (* StdModelDirectory *)
  584.     PROCEDURE (d: StdModelDirectory) New (): Model;
  585.         VAR m: StdModel;
  586.     BEGIN
  587.         NEW(m); m.Init; m.s := "";  m.i := 0; RETURN m
  588.     END New;
  589.     (* View *)
  590.     PROCEDURE (v: View) Internalize* (VAR rd: Stores.Reader);
  591.         VAR version: SHORTINT;
  592.     BEGIN
  593.         v.Internalize^(rd);
  594.         IF rd.cancelled THEN RETURN END;
  595.         rd.ReadVersion(0, 0, version)
  596.     END Internalize;
  597.     PROCEDURE (v: View) Externalize* (VAR wr: Stores.Writer);
  598.     BEGIN
  599.         v.Externalize^(wr);
  600.         wr.WriteVersion(0)
  601.     END Externalize;
  602.     PROCEDURE (v: View) InitModel* (m: Model);
  603.     BEGIN
  604.         HALT(127)
  605.     END InitModel;
  606.     (* Directory *)
  607.     PROCEDURE (d: Directory) New* (m: Model): View;
  608.     BEGIN
  609.         HALT(127)
  610.     END New;
  611.     (* StdView *)
  612.     PROCEDURE (v: StdView) InitModel (m: Model);
  613.     BEGIN
  614.         v.m := m
  615.     END InitModel;
  616.     PROCEDURE (v: StdView) Internalize (VAR rd: Stores.Reader);
  617.         VAR version: SHORTINT; s: Stores.Store;
  618.     BEGIN
  619.         v.Internalize^(rd);            (* internalize the base type *)
  620.         IF rd.cancelled THEN RETURN END;
  621.         rd.ReadVersion(0, 0, version);
  622.         IF rd.cancelled THEN RETURN END;
  623.         rd.ReadStore(s);                (* read a store *)
  624.         v.InitModel(s(Model))
  625.     END Internalize;
  626.     PROCEDURE (v: StdView) Externalize (VAR wr: Stores.Writer);
  627.     BEGIN
  628.         v.Externalize^(wr);            (* externalize the base type *)
  629.         wr.WriteVersion(0);
  630.         wr.WriteStore(v.m)
  631.     END Externalize;
  632.     PROCEDURE (v: StdView) CopyModelFrom (source: Views.View; shallow: BOOLEAN);
  633.         VAR m: Model;
  634.     BEGIN
  635.         v.CopyModelFrom^(source, shallow);    (* copy the base type *)
  636.         WITH source: StdView DO
  637.             IF shallow THEN
  638.                 m := source.m        (* both views share the same model *)
  639.             ELSE
  640.                 m := source.m.Clone(); m.CopyFrom(source.m)    (* both views get their own model copies *)
  641.             END;
  642.             v.InitModel(m)
  643.         END
  644.     END CopyModelFrom;
  645.     PROCEDURE (v: StdView) Restore (f: Views.Frame; l, t, r, b: LONGINT);
  646.         VAR s: ARRAY 256 OF CHAR;
  647.     BEGIN
  648.         v.m.GetString(s);
  649.         f.DrawString(d, d, Ports.black, s, Fonts.dir.System())
  650.     END Restore;
  651.     PROCEDURE (v: StdView) ThisModel (): Models.Model;
  652.     BEGIN
  653.         RETURN v.m
  654.     END ThisModel;
  655.     PROCEDURE (v: StdView) HandleModelMsg (VAR msg: Models.Message);
  656.     BEGIN
  657.         Views.Update(v, Views.keepFrames)    (* restore v in any frame that displays it *)
  658.     END HandleModelMsg;
  659.     PROCEDURE (v: StdView) HandleCtrlMsg (f: Views.Frame; VAR msg: Controllers.Message;
  660.                                         VAR focus: Views.View);
  661.         VAR m: Models.Model;
  662.     BEGIN
  663.         WITH msg: Controllers.PollOpsMsg DO
  664.             msg.valid := {Controllers.pasteChar}    (* tell that v accepts typing *)
  665.         | msg: Controllers.EditMsg DO
  666.             IF msg.op = Controllers.pasteChar THEN
  667.                 m := v.ThisModel();    (* fetch model *)
  668.                 m(Model).Insert(msg.char)    (*  undoable insertion *)
  669.             END
  670.         ELSE                            (* ignore other messages *)
  671.         END
  672.     END HandleCtrlMsg;
  673.     (* StdDirectory *)
  674.     PROCEDURE (d: StdDirectory) New* (m: Model): View;
  675.         VAR v: StdView;
  676.     BEGIN
  677.         NEW(v); v.Init(); v.InitModel(m); RETURN v
  678.     END New;
  679.     PROCEDURE Deposit*;
  680.         VAR v: View;
  681.     BEGIN
  682.         v := dir.New(mdir.New());
  683.         Views.Deposit(v)
  684.     END Deposit;
  685.     PROCEDURE SetDir* (d: Directory);
  686.     BEGIN
  687.         dir := d
  688.     END SetDir;
  689.     PROCEDURE Init;
  690.         VAR md: StdModelDirectory; d: StdDirectory;
  691.     BEGIN
  692.         NEW(md); mdir := md;
  693.         NEW(d); dir := d
  694.     END Init;
  695. BEGIN
  696.     Init
  697. END TutorialEx16.
  698. The separation of a type's definition from its implementation is recommended in the design of new Oberon/F subsystems. However, simple view types which won't become publicly available or which are not meant to be extended can certainly dispense with this additional effort.
  699. TextControllers.StdCtrlDesc
  700. TextControllers.ControllerDesc
  701. Containers.ControllerDesc
  702. Controllers.ControllerDesc
  703. Geneva
  704. Documents.ControllerDesc
  705.